#include "stdafx.h"
#include "RoomTimer.h"
#include "WindowsUtility.h"
#include "IActiveObject.h"

namespace RoomFW {

RoomTimer::IdMgr::IdMgr() :
	nextId_(InvalidTimerId),
	inUse_()
{
}

RoomTimer::IdMgr::~IdMgr()
{
}

RoomTimer::IdMgr::TimerIdPtr RoomTimer::IdMgr::next()
{
	TimerIdPtr retVal;
	{CriticalSectionGuard g(cs_);
		TimerId retId = ++nextId_;
		while ((inUse_.end() != inUse_.find(retId)) || (InvalidTimerId == retId))
		{
			retId = ++nextId_;
		}
		inUse_.insert(retId);
		retVal.reset(new IdMgrId(retId, this));
	}
	return retVal;
}

void RoomTimer::IdMgr::release(TimerId timerId)
{
	CriticalSectionGuard g(cs_);
	VERIFY(inUse_.erase(timerId) > 0);
}

RoomTimer::IdMgr::IdMgrId::IdMgrId(TimerId id, IdMgr* mgr) :
	id_(id),
	mgr_(mgr)
{
}

RoomTimer::IdMgr::IdMgrId::~IdMgrId()
{
	mgr_->release(id_);
}

RoomTimer::IdMgr::IdMgrId::operator RoomTimer::TimerId()
{
	return id_;
}


RoomTimer::RoomTimer(IdMgr* idMgr) :
	idMgr_(idMgr),
	maxSize_(0),
	timerData_(),
	timerMap_(),
	handleMap_(),
	cs_(),
	setEvent_()
{
}

RoomTimer::~RoomTimer()
{
}

bool RoomTimer::initialize(size_t maxSize)
{
	ASSERT(maxSize > 0); // To prevent overflow and what would be the point?
	maxSize_ = maxSize - 1; // -1 for the non-timer event.
	return setEvent_.create();
}

RoomTimer::TimerId RoomTimer::setTimer(IActiveObject* handler,
									   RoomMessageSP expirationMessage,
									   const TimeValue& expireIn,
									   const TimeValue& repeatEvery)
{
	RoomTimer::TimerId retVal = InvalidTimerId;

	// Check windows wait object limit.  Create the timer object.  Set the timer.
	TimerData timerData;
	if ( !this->full() 
		&& expireIn >= TimeValue(0,0)
		&& timerData.timer.create() 
		&& timerData.timer.set(expireIn, repeatEvery) )
	{
		// Save timer data.
		timerData.timerId = idMgr_->next();
		timerData.handler = handler;
		timerData.repeat = (repeatEvery != 0);
		timerData.cancel = false;
		timerData.expirationMessage = expirationMessage;
		{CriticalSectionGuard guard(cs_);
		{
			TimerDataList::iterator i = timerData_.insert(timerData_.end(), timerData);
			timerMap_[*(timerData.timerId)] = i;
			handleMap_[timerData.timer] = i;
			setEvent_.set();
		}}
		retVal = *(timerData.timerId);
	}
	return retVal;
}


bool RoomTimer::cancelTimer(TimerId timerId)
{
	bool retVal = false;
	{CriticalSectionGuard guard(cs_);
	{
		// Look up the timer id to cancel.
		TimerIdMap::iterator iMap = timerMap_.find(timerId);
		if (timerMap_.end() != iMap)
		{
			retVal = true;

			// We don't want to cleanup a handle we're waiting on.  Just mark it canceled.
			TimerDataList::iterator iData = iMap->second;
			iData->cancel = true;

			// We might as well clean it up as soon as possible.
			setEvent_.set();
		}
	}}
	return retVal;
}

bool RoomTimer::full()
{
	bool retVal = false;
	{CriticalSectionGuard guard(cs_);
	{
		retVal = (timerData_.size() >= maxSize_);
	}}
	return retVal;
}

size_t RoomTimer::size()
{
	size_t retVal = 0;
	{CriticalSectionGuard guard(cs_);
	{
		retVal = timerData_.size();
	}}
	return retVal;
}

void RoomTimer::addHandles(HandleArray& handles)
{
	{CriticalSectionGuard guard(cs_);
	{
		// Windows signals lowest index first, so rotate.
		if (!timerData_.empty())
		{
			timerData_.push_front(timerData_.back());
			timerMap_[*(timerData_.front().timerId)] = timerData_.begin();
			handleMap_[timerData_.front().timer] = timerData_.begin();
			timerData_.pop_back();
		}

		// Add set event handle.
		handles.push_back(setEvent_);

		// Add timer handles.
		TimerDataList::iterator i = timerData_.begin();
		while (timerData_.end() != i)
		{
			if (!i->cancel) // Check for cancelations.
			{
				handles.push_back(i->timer);
				++i;
			}
			else // Clean up canceled timers.
			{
				timerMap_.erase(*(i->timerId));
				handleMap_.erase(i->timer);
				i = timerData_.erase(i);
			}
		}
	}}
}

void RoomTimer::signal(HANDLE handle)
{
	// Ignore setEvent_.  It's just to stop the wait and re-start with updated handles.
	if (handle != setEvent_)
	{
		// Don't call ActiveObject inside Critical Section to avoid cycle.
		IActiveObject* handler = 0;
		RoomMessageSP expirationMessage;
		{CriticalSectionGuard guard(cs_);
		{
			// Find the timer data.
			TimerHandleMap::iterator iMap = handleMap_.find(handle);
			ASSERT(handleMap_.end() != iMap); // by this class
			TimerDataList::iterator iData = iMap->second;
			if (!iData->cancel)
			{
				// Save the handler data.
				handler = iData->handler;
				expirationMessage = iData->expirationMessage;

				// Mark one-shot timers for cleanup.
				if (!iData->repeat)
				{
					iData->cancel = true;
				}
			}
		}}

		// Put the expiration message.
		if (0 != handler)
		{
			handler->put(expirationMessage);
		}
	}
}

void RoomTimer::pulse()
{
	std::list<HANDLE> handles;
	{CriticalSectionGuard guard(cs_);
	{
		TimerDataList::iterator i = timerData_.begin();
		while (timerData_.end() != i)
		{
			if (!i->cancel) // Check for cancelations.
			{
				if (i->timer.wait(0))
				{
					handles.push_back(i->timer);
				}
				++i;
			}
			else // Clean up canceled timers.
			{
				timerMap_.erase(*(i->timerId));
				handleMap_.erase(i->timer);
				i = timerData_.erase(i);
			}
		}
	}}
	BOOST_FOREACH(HANDLE handle, handles)
	{
		this->signal(handle);
	}
}

}
